<?php

namespace app\admin\model;

use think\Model;
use think\model\Collection;
use app\common\traits\NodeTrait;
use think\Db;
use BlueM\Tree;
use app\admin\model\Admin;

class AuthRule extends Model
{
    use NodeTrait;
    /**
     * 开启自动写入时间戳字段
     */
    protected $autoWriteTimestamp = 'integer';
    /** 类型:功能 */
    const TYPE_FUNC = 'func';
    /** 类型:配置 */
    const TYPE_CONF = 'conf';
    /** 类型:扩展 */
    const TYPE_EXT = 'ext';
    /** 类型:开发 */
    const TYPE_DEV = 'dev';

    /**
     * @param boolean 是否激活.
     */
    private $_active = false;

    public static $statusOptions = [
        '0' => '禁用', '1' => '启用'
    ];

    public function getStatusDesc()
    {
        return self::$statusOptions[$this->getData('status')];
    }

    public function getFancyTreeNode()
    {
        $arr = [
            'key' => $this->id,
            'title' => $this->title,
             // 如果 icon 是空字符串, 则前台显示的也是空...这里给一个默认图标会好看一点
            'icon' => $this->icon ?: 'fa fa-link',
            'rule' => $this->rule,
            'weight' => $this->weight,
            'status' => $this->getData('status'),
            'is_menu' => (bool)$this->is_menu,
        ];
        return $arr;
    }

    /**
     * 获取规则的标识符.
     * 用于 casbin.
     * @return string
     */
    public function getRuleIdentifier()
    {
        return 'rule.'.$this->id;
    }

    /**
     * 获取规则的标识符.
     * 用于 casbin.
     * @param int $id
     * @return string
     */
    public static function getRuleIdentifierById($id)
    {
        return 'rule.'.$id;
    }

    /**
     * 批量创建规则.
     * @param array|array[] $nodes 规则数据
     * @param int $pid 规则的父id
     */
    public static function createTree($nodes, $pid = 0)
    {
        if (!isset($nodes[0])) {
            $nodes = [$nodes];
        }

        foreach ($nodes as $node) {
            if (!is_array($node)) {
                continue;
            }
            unset($node['id']);
            $node['pid'] = $pid;
            $n = self::create($node);

            if (isset($node['children']) && is_array($node['children']) && $node['children']) {
                self::createTree($node['children'], $n->id);
            }
        }
    }

    /**
     * 返回类型选项.
     * @return array
     */
    public static function getRuleTypeOptions()
    {
        return [
            self::TYPE_FUNC => '功能',
            self::TYPE_CONF => '配置',
            // self::TYPE_EXT => '扩展',
            self::TYPE_DEV => '开发',
        ];
    }

    /**
     * 返回显示的类型.
     * @return string[]
     */
    public static function getDisplayTypes()
    {
        return [self::TYPE_FUNC, self::TYPE_CONF, self::TYPE_DEV];
    }

    /**
     * 获取管理员对应的菜单.
     * 对于 未启用(status=0) 或者 不是菜单(is_menu=0) 的节点 也会返回.
     * @param string[] $types 显示的类型.
     * @param string $activeRule 当前激活的规则名.
     * @param Admin $admin 当前用户, 如果有值, 则返回的菜单仅仅是该用户可以访问的.
     * @return Collection|AuthRule[] 返回 AuthRule 的集合, 其中的节点关系已经建立好.
     */
    public static function getMenuTree($types = true, $activeRule = '_none_', Admin $admin = null)
    {
        if ($types === true) {
            $types = self::getDisplayTypes();
        }
        $rules = self::select();
        $rulesMap = [];
        foreach ($rules as $rule) {
            $rulesMap[$rule->id] = $rule;
        }
        $rootRules = [];
        $active = null;
        foreach ($rules as $rule) {
            $pid = $rule->pid;
            // 注意这里不能用 $rule->type 来获取 type 字段的数据
            if ((int)$pid === 0 && in_array($rule->getData('type'), $types)) {
                if ($admin) {
                    if ($admin->canAccess($rule->id)) {
                        $rootRules[$rule->id] = $rule;
                    }
                } else {
                    $rootRules[$rule->id] = $rule;
                }
            } else {
                if (isset($rulesMap[$pid])) {
                    if ($admin) {
                        if ($admin->canAccess($rule->id)) {
                            $rulesMap[$pid]->addChildren($rule);
                        }
                    } else {
                        $rulesMap[$pid]->addChildren($rule);
                    }
                }
            }
            if ($rule->rule === $activeRule) {
                $active = $rule;
            }
        }
        // trace($activeRule);
        if ($active) {
            $active->setActive(true);
            // 把当前访问的规则(路由)绑定到容器的 'active_rule' 上, 方便在外部用 app('active_rule') 来访问
            bind('active_rule', $active);
        } else {
            $unknown = self::getUnknownRule();
            bind('active_rule', $unknown);
        }
        return new Collection($rootRules);
    }

    /**
     * 返回一维树状选项.
     * @param int|null $exceptId 排除的ID, 一般来说编辑操作时, 我们希望排除当前编辑的节点(及其后代节点).
     * @return array ['id' => 'titile']
     */
    public static function getFlatTreeOptions($exceptId = null)
    {
        // $rules = AuthRuleModel::order('weight', 'desc')->select();
        $rules = Db::name('auth_rule')->where(function ($query) use ($exceptId) {
            if ($exceptId) {
                $query->where('id', '<>', $exceptId);
            }
        })->order('weight', 'desc')->select();
        // 这里 buildWarningCallback 指定为一个空回调, 是为了避免因为树状结构父子节点数据不完整而抛出异常 (直接忽略掉不完整的部分)
        $tree = new Tree($rules, ['parent' => 'pid', 'buildWarningCallback' => function(){} ]);

        $nodes = $tree->getNodes();
        $options = ['0' => '无'];
        foreach ($nodes as $node) {
            $level = $node->getLevel();
            if ($level > 1) {
                $options[$node->id] = str_repeat('&nbsp;', ($level - 2)*4).'┗&nbsp;&nbsp;'.$node->title;
            } else {
                $options[$node->id] = $node->title;
            }
        }
        return $options;
    }

    /**
     * 返回一个未知的 rule.
     * 一般用于数据库中未定于 rule 的路由.
     * @return self
     */
    public static function getUnknownRule()
    {
        return new self([
            'pid' => 0,
            'title' => '未定义的Rule',
            'rule' => '',
        ]);
    }

    /**
     * 当前规则(路由)是否为当前请求的页面的路由(或者是该路由的先辈).
     * @return boolean
     */
    public function isActive()
    {
        return $this->_active;
    }

    /**
     * 设置当前 rule 的激活状态, 如果当前 rule 是激活状态, 则其所有祖先 rule 都是 激活状态.
     * 激活状态一般用于菜单中高亮显示.
     * @param boolean $active
     */
    public function setActive($active = true)
    {
        $this->_active = $active;
        if ($this->_parent) {
            $this->_parent->setActive($active);
        }
    }

    /**
     * 是否有有效的子菜单(至少有一个有效子菜单时才返回 true).
     * 子规则被禁用 或者 不是菜单 的情况都不会被视作为 有效子菜单.
     * @return boolean
     */
    public function hasValidSubMenu()
    {
        if ($this->hasChildren()) {
            foreach ($this->getChildren() as $child) {
                if ($child->isEnabled() && $child->isMenu()) {
                    return true;
                }
            }
        }

        return false;
    }

    /**
     * 返回有效的子级菜单.
     * @return self[]
     */
    public function getValidSubMenu()
    {
        $menu = [];
        foreach ($this->getChildren() as $child) {
            if ($child->isEnabled() && $child->isMenu()) {
                $menu[] = $child;
            }
        }

        return $menu;
    }

    /**
     * 当前规则是否在后台菜单中显示.
     * @return boolean
     */
    public function isMenu()
    {
        return (int)$this->is_menu === 1;
    }

    /**
     * 当前规则是否启用.
     * @return boolean
     */
    public function isEnabled()
    {
        return (int)$this->status === 1;
    }

    public function getIcon()
    {
        return $this->icon ?: 'fa fa-link';
    }

    /**
     * 返回 规则 的 url.
     * TODO: 目前这里不能自动获取有参数路由需要的参数...
     * @return string
     */
    public function getLink()
    {
        return $this->rule ? url($this->rule) : '#';
    }
}
